// interrupts.c
// Joseph Craig, Rebecca Rush, Steven Andryzcik
// Version 6 - 12/08/2011

#include "inter.h"
#include "io.h"
#include <string.h>

#define NEXT_PROCESS process_add_count++

extern unsigned long process_add_count;
extern dcb device1;
extern iocb
	port_cb,
	terminal_cb;
extern pq
		ready,
		blocked,
		suspended,
		port_queue,
		terminal_queue;
unsigned char sys_stack[SYS_STACK_SIZE];
unsigned short 
	ss_save = NULL,
	sp_save = NULL,
	new_ss = NULL,
	new_sp = NULL;
	
ppcb
	cop = NULL,
	to_resume = NULL;

context *context_ptr;
params  *param_ptr;
int dispatch_error;
int sys_len;
int high_called = 0;
int high_dispatched = 0;

/*****************************Interrupt Functions******************************/
// dispatch
// Parameters: none
// Return: void
// Functions called: pq_dequeue, FP_SEG, FP_OFF
// Dispatch is responsible for running processes from the ready queue. 
void interrupt dispatch(){
	//If ss_save is NULL, save the stack pointers to the save variables
	if(ss_save == NULL){
		ss_save = _SS;
		sp_save = _SP;
	}
	// If a pcb is found, perform a context switch to allow the process to run
	if((dispatch_error = pq_dequeue(&ready, (void**)&cop)) == OK){
		new_ss = FP_SEG(cop->stack_top);
		new_sp = FP_OFF(cop->stack_top);
		_SS = new_ss;
		_SP = new_sp;
	}
	// Returns a pq_dequeue error
	else if(dispatch_error == ERR_PQUEUE_NULLARG){
		sys_len = 51;
		sys_req(
			WRITE,
			TERMINAL,
			"\n\tError: Invalid argument passed to pq_dequeue.\n\n",
			&sys_len
		);
		sys_exit();
	}
	//If no pcb is ready, restore the mpx state
	else{
		// Set the cop variable to null
		cop = NULL;
		// Restore the stack pointers using the save variables
		_SS = ss_save;
		_SP = sp_save;
		ss_save = NULL;
		sp_save = NULL;
	}
}

// handle_port
// Parameters: none
// Return: void
// Functions called: outportb, inportb, write_out, read_in
// First level interrupt handler is called when an I/O interrupt on 
// the comport is generated. Determines the cause of the interrupt and 
// calls the appropriate second level interrupt handler. 
void interrupt handle_port(){
	unsigned char interrupt_source;
	
	/* If the port is not open, clear the interrupt and return.
	 */
	if (device1.flag != OPEN) {
		outportb(PIC_CMD, PIC_CMD);
		return;
	}
	
	/* Read the Interrupt ID register to determine the exact cause of the
	 * interrupt. Bit 0 must be a 0 if the interrupt was actually caused by the
	 * serial port. In this case, bits 2 and 1 indicate the specific interrupt
	 * type as follows:
	 *
	 * Bit2  Bit1  InterruptType
	 * ----------------------------------
	 *    0     0  Modem Status Interrupt
	 *    0     1  Output Interrupt
	 *    1     0  Input Interrupt
	 *    1     1  Line Status Interrupt
	 */
	interrupt_source = inportb(COM1_INT_ID_REG);
	if (interrupt_source & 1) {
		outportb(PIC_CMD, PIC_CMD);
		return;
	}
	
	interrupt_source = (interrupt_source >> 1) & 3;
	
	/* Call the appropriate second-level handler.
	 */
	switch (interrupt_source) {
		case MODEM_STATUS:
		case LINE_STATUS:
			break;
		
		case OUTPUT:
			write_out();
			break;
		
		case INPUT:
			read_in();
			break;
	}
	
	/* Clear the interrupt by sending EOI to the PIC command register.
	 */
	outportb(PIC_CMD, PIC_CMD);
}

// kill_process
// Paramaters: process name
// Return: void
// Functions called: pq_find, sys_exit, print,
// pq_remove, sys_free_mem
// Removes a process from the queue it is currently in
// and frees the memory associated with it.
void /*over*/kill_process(char *name) {
	int  error_code;
	ppcb target = NULL;
	ppq  queue  = NULL;
	
	/* If the arguments are invalid beyond the argument count, there must be
	 * something wrong with the calling code.
	 */
	if (
		(error_code = pq_find(
			&ready,
			1,
			(void **) &name,
			(void **) &target
		)) == ERR_PQUEUE_NULLARG
	) {
		print(
			"\n\tFATAL ERROR: Terminate(int, char **) improperly called.\n\n",
			TRUE
		);
		sys_exit();
	}

	/* Determine which queue (if any) contains the requested process.
	 */
	if (error_code == OK) {
		queue = &ready;
	} else if (
		(error_code = pq_find(
			&blocked,
			1,
			(void **) &name,
			(void **) &target
		)) == OK
	) {
		queue = &blocked;
	} else if (
		(error_code = pq_find(
			&suspended,
			1,
			(void **) &name,
			(void **) &target
		)) == OK
	) {
		queue = &suspended;
	} else if (error_code == ERR_PQUEUE_ITEMNOTFOUND) {
		print("\n\tError: Could not find the requested process.\n\n", TRUE);
		return;
	}
	
	//Determine that the process is not a SYSTEM process
	if(target->class == SYSTEM){
		print("SYSTEM processes cannot be terminated\n\n", TRUE);
		return;
	}
	
	/* If the code has reached this point, then the process exists.  Remove any
	 * and all of its occurences in any queue, process or I/O request.
	 */
	if (terminal_cb.current != NULL && terminal_cb.current->mypcb == target) {
		terminal_cb.current = NULL;
		terminal_cb.event_flag = 1;
	} else if (port_cb.current != NULL && port_cb.current->mypcb == target) {
		port_cb.current = NULL;
		port_cb.event_flag = 1;
	}
	
	while (
		(error_code = pq_remove(&terminal_queue, (void *) target)) == OK
	);
	while (
		(error_code = pq_remove(&port_queue, (void *) target)) == OK
	);
	
	while (
		(error_code = pq_remove(&ready, (void *) target)) == OK
	);
	while (
		(error_code = pq_remove(&blocked, (void *) target)) == OK
	);
	while (
		(error_code = pq_remove(&suspended, (void *) target)) == OK
	);
	
	sys_free_mem(target);
}

// suspend_process
// Parameters: process name
// Return: void
// Functions called: pq_find, print, pq_remove, pq_enqueue
// Suspends a process and places it in the blocked queue. 
void suspend_process(char *name) {
	int  error_code;
	ppcb target = NULL;
	ppq  queue  = NULL;
	
	/* If the arguments are invalid beyond the argument count, there must be
	 * something wrong with the calling code.
	 */
	if (
		(error_code = pq_find(
			&ready,
			1,
			(void **) &name,
			(void **) &target
		)) == ERR_PQUEUE_NULLARG
	) {
		print(
			"\n\tFATAL ERROR: Suspend(int, char **) improperly called.\n\n",
			TRUE
		);
		sys_exit();
	}

	/* Determine which queue (if any) contains the requested process.
	 */
	if (error_code == OK) {
		queue = &ready;
	} else if (
		(error_code = pq_find(
			&blocked,
			1,
			(void **) &name,
			(void **) &target
		)) == OK
	) {
		queue = &blocked;
	} else if (
		(error_code = pq_find(
			&suspended,
			1,
			(void **) &name,
			(void **) &target
		)) == OK
	) {
		queue = &suspended;
	} else if (error_code == ERR_PQUEUE_ITEMNOTFOUND) {
		print("\n\tError: Could not find the requested process.\n\n", TRUE);
		return;
	}
	
	//Determine that the process is not a SYSTEM process
	if(target->class == SYSTEM){
		print("\n\tSYSTEM processes cannot be suspended.\n\n", TRUE);
		return;
	}
	
	/* If the code has reached this point, then the process exists.  Remove any
	 * and all of its occurences in any queue, process or I/O request.
	 */
	if (terminal_cb.current != NULL && terminal_cb.current->mypcb == target) {
		terminal_cb.current = NULL;
		terminal_cb.event_flag = 1;
	} else if (port_cb.current != NULL && port_cb.current->mypcb == target) {
		port_cb.current = NULL;
		port_cb.event_flag = 1;
	}
	
	while (
		(error_code = pq_remove(&terminal_queue, (void *) target)) == OK
	);
	while (
		(error_code = pq_remove(&port_queue, (void *) target)) == OK
	);
	
	while (
		(error_code = pq_remove(&ready, (void *) target)) == OK
	);
	while (
		(error_code = pq_remove(&blocked, (void *) target)) == OK
	);
	while (
		(error_code = pq_remove(&suspended, (void *) target)) == OK
	);
	
	target->state = BLOCKED;
	pq_enqueue(&blocked, target);
}

// sys_call
// Parameters: none
// Return: void
// Functions called: MK_FP, FP_SEG, FP_OFF, sizeof,
// trm_getc, sys_free_mem, pq_remove, pq_enqueue,
// pq_dequeue, prime_request, pcb_free, pcb_insert, printf,
// io_scheduler, sys_exit, dispatch
//  Sys_calls is invoded by mpx and interprets system call parameters,
// puts the currently running proces back in a queue or deletes it,
// depending on the interrupt. Finally, it calls dispatch to get the next process.
void interrupt sys_call() {
	/* Save the current stack information to the current operating process's
	 * stack.
	 */
	cop->stack_top = MK_FP(_SS, _SP);
	// Decrement stack_top to point to new empty?
	
	/* Switch to sys_call's stack.
	 */
	new_ss =  FP_SEG(sys_stack);
	new_sp =  FP_OFF(sys_stack);
	new_sp += SYS_STACK_SIZE;
	_SS    =  new_ss;
	_SP    =  new_sp;
	
	/* Extract the context and parameters from the stack.
	 */
	context_ptr = (context *) cop->stack_top;
	param_ptr   = (params *) (cop->stack_top + sizeof(context));

	/* Flush the keyboard.buffer into the MPX buffer.
	 */
	trm_getc();
	
	/* Check to see whether the currently operating com port request has
	 * finished.
	 */
	if (port_cb.event_flag == 1) {
		/* Clear the event flag.
		 */
		port_cb.event_flag = 0;
		
		if (port_cb.current != NULL) {
			/* "Remove the IOD at the head of the IOCB's waiting queue and free the
			 * IOD's memory." - slide
			 *
			 * That's not how we did things.  We put the allocated IOD straight into
			 * the IOCB, and free the memory for a request once it's finished.
			 */
			to_resume = port_cb.current->mypcb;
			sys_free_mem(port_cb.current);
			port_cb.current = NULL;
			
			/* Unblock the process who had requested the IOD you just freed, this
			 * should put it in the ready queue. The process that requested the IOD
			 * is maintained as a field in the IOD
			 */
			pq_remove(&blocked, (void *) to_resume);
			to_resume->state = READY;
			
			if (to_resume->class != SYSTEM) {
				to_resume->added = NEXT_PROCESS;
			}
			
			pq_enqueue(&ready, (void *) to_resume);
			to_resume = NULL;
		}
			
		/* Process the next I/O request for that device.
		 */
		if (!pq_is_empty(&port_queue)) {
			pq_dequeue(&port_queue, (void **) &(port_cb.current));
			prime_request(COM_PORT);
		}
	}
	
	/* Check to see whether the currently operating terminal request has
	 * finished.
	 */
	if (terminal_cb.event_flag == 1) {
		/* Clear the event flag.
		 */
		terminal_cb.event_flag = 0;
		
		if (terminal_cb.current != NULL) {
			/* "Remove the IOD at the head of the IOCB's waiting queue and free the
			 * IOD's memory." - slide
			 *
			 * That's not how we did things.  We put the allocated IOD straight into
			 * the IOCB, and free the memory for a request once it's finished.
			 */
			to_resume = terminal_cb.current->mypcb;
			sys_free_mem(terminal_cb.current);
			terminal_cb.current = NULL;

			/* Unblock the process who had requested the IOD you just freed, this
			 * should put it in the ready queue. The process that requested the IOD
			 * is maintained as a field in the IOD
			 */
			pq_remove(&blocked, (void *) to_resume);
			to_resume->state = READY;
			
			if (to_resume->class != SYSTEM) {
				to_resume->added = NEXT_PROCESS;
			}
			
			pq_enqueue(&ready, (void *) to_resume);
			to_resume = NULL;
		}
			
		/* Process the next I/O request for that device.
		 */
		if (!pq_is_empty(&terminal_queue)) {
			pq_dequeue(&terminal_queue, (void **) &(terminal_cb.current));
			prime_request(TERMINAL);
		}
	}
	
	/* Determine what to do based upon the request's operation type.
	 */
	switch(param_ptr->op_code) {
		case EXIT:
			/*
			sys_len = 30;
			sys_req(
				WRITE,
				TERMINAL,
				"Received termination request.\n",
				&sys_len
			);
			//*/
			pcb_free(cop);
			printf("Received termination request from %s.\n", cop->name);
			//pcb_free(cop);
			break;
		
		case IDLE:
			if (cop->class != SYSTEM) {
				cop->added = NEXT_PROCESS;
			}
			pcb_insert(READY, cop);
			break;
		
		case READ:
		case WRITE:
		case CLEAR:
		case GOTOXY:
			io_scheduler();
			break;
		
		default:
			printf(
				"\n\tFATAL ERROR: invalid interrupt operation type %d\n\n",
				param_ptr->op_code
			);
			sys_exit();
	}
	
	/* Save the result of the operation into the COP's result register, clean up
	 * for a dispatch, then attempt to dispatch the next process.
	 */
	context_ptr->AX = OK;
	cop = NULL;
	dispatch();
}

